/*
 * SpeedoGradient - Speedometer and gradient  meter application for Android 
 * Copyright 2013 Guilhem VALENTIN
 * 
 * This file is part of SpeedoGradient
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with SpeedoGradient. If not, see <http://www.gnu.org/licenses/>.
 */
package com.gvalentin.speedogradient.view;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;

import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RadialGradient;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import com.gvalentin.speedogradient.data.SpeedoGradientData;
import com.gvalentin.speedogradient.util.Fonts;
import com.gvalentin.speedogradient.util.PreferenceUtil;
import com.gvalentin.speedogradient.util.SpeedUnit;

/**
 * Speedometer (+ incline) view
 * @author guilhem
 */
public class Speedometer extends View {

	private static final String TAG = Speedometer.class.getSimpleName();

	// Margin between edges and speedometer rim 
	private static final float BORDER_MARGIN = 0.02f;
	// Size of white RIM
	private static final float RIM_SIZE = 0.15f;
	// Size of ticks in RIM
	private static final float RIM_TICK_TINY = 0.035f;
	private static final float RIM_TICK_SHORT = 0.035f;
	private static final float RIM_TICK_LONG = 0.055f;
	
	// Data
	private SpeedoGradientData data = null;
	
	// Position of speed hand
	private double speedHandPosition = 0.0f;
	private double speedHandTarget = 0.0f;
	private double speedHandVelocity = 0.0f;
	private long lastSpeedHandMoveTime = -1L;
	private double speedHandMaxPosition = 0.0f;

	// Drawing tools
	private RectF allRect;
	private RectF rimRect;
	private Paint rimPaint;
	private Paint rimShadowPaint;
	
	private RectF faceRect;
	private Paint facePaint;
	private Paint speedUnitTextPaint;
	
	private Paint scaleTinyTickPaint;
	private Paint scaleShortTickPaint;
	private Paint scaleLongTickPaint;
	private Paint scaleTextPaint;
	private RectF scaleRect;

	private Paint speedHandPaint;
	private Paint averageSpeedHandPaint;
	private Paint maxSpeedHandPaint;	
	private Path speedHandPath;
	private Path averageSpeedHandPath;

	private Paint inclinePaint;
	private Path inclinePath;
	
	private Paint speedTextPaint;
	private Paint inclineTextPaint;
	
	private Paint distancePaint;
	
	private Paint backgroundPaint; 

	/** holds the cached static part */	
	private Bitmap background;
	
	// Number formatting tools
	private static final DecimalFormat rounder0 = new DecimalFormat("#");
	private static final DecimalFormat rounderOdo = new DecimalFormat("0000.0", new DecimalFormatSymbols(Locale.ENGLISH));
	
	/** Maximum speed in scale */
	private int maxScaleSpeed = 50;
	
	/**
	 * Constructor
	 */
	public Speedometer(Context context) {
		super(context);
		init();
	}

	/**
	 * Constructor
	 */
	public Speedometer(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	/**
	 * Constructor
	 */
	public Speedometer(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init();
	}
	

	/**
	 * Init configuration. This method must be called at activity initialization,
	 * and each time the preferences are modified.
	 */
	public void configure(SharedPreferences sharedPreferences) {
		maxScaleSpeed = PreferenceUtil.getPreference(sharedPreferences, "pref_maxSpeed", 50);
	}
	
	/** Initialize the view */
	private void init() {
		initDrawingTools();
	}

	/**
	 * Init the background of the view
	 */
	private void initDrawingTools() {
		// Meter total rect
		allRect = new RectF(0.0f, 0.0f, 1.0f, 1.0f);
		
		// White rim rect
		rimRect = new RectF(BORDER_MARGIN, BORDER_MARGIN, 1.0f - BORDER_MARGIN, 1.0f - BORDER_MARGIN);

		// White rim color
		rimPaint = new Paint();
		rimPaint.setAntiAlias(true);
		rimPaint.setStyle(Paint.Style.FILL);
		rimPaint.setColor(Color.WHITE);

		// Rim shadow
		rimShadowPaint = new Paint();
		rimShadowPaint.setAntiAlias(true);
		rimShadowPaint.setShader(new RadialGradient(0.5f, 0.5f, rimRect.width() / 2.0f, 
				   new int[] { 0x60000500, 0x00000500, 0x00000000, 0x00000000, 0x00000500, 0x80000500 },
				   new float[] { 0.70f, 0.74f, 0.74f, 0.94f, 0.94f, 1f },
				   Shader.TileMode.MIRROR));
		rimShadowPaint.setStyle(Paint.Style.FILL);
		
		// Rect for scale in the rim
		scaleRect = rimRect;

		// Scale paint: tiny ticks
		scaleTinyTickPaint = new Paint();
		scaleTinyTickPaint.setStyle(Paint.Style.STROKE);
		scaleTinyTickPaint.setColor(Color.BLACK);
		scaleTinyTickPaint.setStrokeWidth(0.002f);
		scaleTinyTickPaint.setAntiAlias(true);
		// Scale paint: short ticks
		scaleShortTickPaint = new Paint();
		scaleShortTickPaint.setStyle(Paint.Style.STROKE);
		scaleShortTickPaint.setColor(Color.BLACK);
		scaleShortTickPaint.setStrokeWidth(0.01f);
		scaleShortTickPaint.setAntiAlias(true);
		// Scale paint: long ticks
		scaleLongTickPaint = new Paint();
		scaleLongTickPaint.setStyle(Paint.Style.STROKE);
		scaleLongTickPaint.setColor(Color.BLACK);
		scaleLongTickPaint.setStrokeWidth(0.02f);
		scaleLongTickPaint.setAntiAlias(true);
		// Scale paint: text
		scaleTextPaint = new Paint();
		scaleTextPaint.setColor(Color.BLACK);
		scaleTextPaint.setAntiAlias(true);
		scaleTextPaint.setTextSize(0.07f);
		scaleTextPaint.setTypeface(Fonts.getFontBold(getContext()));
		//scaleTextPaint.setTextScaleX(0.8f);
		scaleTextPaint.setTextAlign(Paint.Align.CENTER);
		scaleTextPaint.setLinearText(true);
		
		// Paint for speed hand
		speedHandPaint = new Paint();
		speedHandPaint.setStyle(Paint.Style.FILL);	
		speedHandPaint.setColor(Color.RED);
		speedHandPaint.setAntiAlias(true);
		speedHandPaint.setShadowLayer(0.01f, 0, -0.005f, 0xc0000000);
		
		// Paint for average speed hand
		averageSpeedHandPaint = new Paint();
		averageSpeedHandPaint.setStyle(Paint.Style.FILL);	
		averageSpeedHandPaint.setColor(Color.RED);
		averageSpeedHandPaint.setAntiAlias(true);
		averageSpeedHandPaint.setShadowLayer(0.01f, 0, 0.005f, 0xc0000000);
		
		// Paint for max speed hand
		maxSpeedHandPaint = new Paint();
		maxSpeedHandPaint.setStyle(Paint.Style.STROKE);
		maxSpeedHandPaint.setStrokeWidth(0.005f);
		maxSpeedHandPaint.setColor(Color.RED);
		maxSpeedHandPaint.setAntiAlias(true);

		// Path for speed hand and max speed hand
		speedHandPath = new Path();
		speedHandPath.moveTo(0.5f - 0.015f, BORDER_MARGIN + RIM_SIZE);
		speedHandPath.lineTo(0.5f - 0.010f, BORDER_MARGIN + RIM_TICK_TINY + 0.007f);
		speedHandPath.lineTo(0.5f, BORDER_MARGIN + RIM_TICK_TINY);
		speedHandPath.lineTo(0.5f + 0.010f, BORDER_MARGIN + RIM_TICK_TINY + 0.007f);
		speedHandPath.lineTo(0.5f + 0.015f, BORDER_MARGIN + RIM_SIZE);
		//speedHandPath.lineTo(0.5f - 0.015f, BORDER_MARGIN + RIM_SIZE);
		
		// Path for average speed hand
		averageSpeedHandPath = new Path();
		averageSpeedHandPath.addArc(rimRect, 266.0f, 8.0f);
		averageSpeedHandPath.lineTo(0.5f, BORDER_MARGIN + RIM_TICK_TINY);
		averageSpeedHandPath.close();
		
		// Draw the black face inside the rim
		faceRect = new RectF();
		faceRect.set(rimRect.left + RIM_SIZE, rimRect.top + RIM_SIZE, 
			     rimRect.right - RIM_SIZE, rimRect.bottom - RIM_SIZE);

		facePaint = new Paint();
		facePaint.setAntiAlias(true);
		facePaint.setStyle(Paint.Style.FILL);
		facePaint.setColor(Color.BLACK);
		
		speedUnitTextPaint = new Paint();
		speedUnitTextPaint.setColor(0xffaaaaaa); // light gray
		speedUnitTextPaint.setAntiAlias(true);
		speedUnitTextPaint.setTypeface(Fonts.getFontRegular(getContext()));
		speedUnitTextPaint.setTextAlign(Paint.Align.CENTER);
		speedUnitTextPaint.setTextSize(0.07f);
		speedUnitTextPaint.setLinearText(true);

		// Face representing incline
		inclinePaint = new Paint();
		inclinePaint.setStyle(Paint.Style.FILL);	
		inclinePaint.setColor(Color.GRAY);
		inclinePaint.setAntiAlias(true);
		inclinePaint.setShader(new LinearGradient(0.0f, faceRect.top+0.75f*(faceRect.bottom-faceRect.top), 0.0f, faceRect.bottom-0.02f,
				   new int[] { Color.GRAY, Color.DKGRAY, Color.BLACK },
				   new float[] { 0.0f, 0.15f, 1.0f },
				   Shader.TileMode.CLAMP));
		RectF inclineRect = new RectF();
		inclineRect.set(faceRect.left + 0.02f, faceRect.top + 0.02f, 
				faceRect.right - 0.02f, faceRect.bottom - 0.02f);
		inclinePath = new Path();
		inclinePath.addArc(inclineRect, 30.0f, 120.0f);
		inclinePath.close();
		
		// Speed text inside the face
		speedTextPaint = new Paint();
		speedTextPaint.setColor(Color.WHITE);
		speedTextPaint.setAntiAlias(true);
		speedTextPaint.setTypeface(Fonts.getFontDynamic(getContext()));
		speedTextPaint.setTextAlign(Paint.Align.CENTER);
		speedTextPaint.setTextSize(0.42f);
		speedTextPaint.setLinearText(true);
		speedTextPaint.setShadowLayer(0.02f, 0, 0, 0x80FFFFFF);
		
		// Incline text inside the face
		inclineTextPaint = new Paint();
		inclineTextPaint.setColor(Color.WHITE);
		inclineTextPaint.setAntiAlias(true);
		inclineTextPaint.setTypeface(Fonts.getFontBold(getContext()));
		inclineTextPaint.setTextAlign(Paint.Align.CENTER);
		inclineTextPaint.setTextSize(0.08f);
		inclineTextPaint.setTextScaleX(1.1f);
		inclineTextPaint.setLinearText(true);
		
		// Distance
		distancePaint = new Paint();
		distancePaint.setColor(Color.WHITE);
		distancePaint.setAntiAlias(true);
		distancePaint.setTypeface(Fonts.getFontOdo(getContext()));
		distancePaint.setTextAlign(Paint.Align.CENTER);
		distancePaint.setTextSize(0.09f);
		distancePaint.setLinearText(true);
		distancePaint.setShadowLayer(0.01f, 0, 0, 0x80FFFFFF);
		
		// Background
		backgroundPaint = new Paint();
		backgroundPaint.setFilterBitmap(true);	
	}

	/**
	 * @see android.view.View#onMeasure(int, int)
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		Log.d(TAG, "Width spec: " + MeasureSpec.toString(widthMeasureSpec));
		Log.d(TAG, "Height spec: " + MeasureSpec.toString(heightMeasureSpec));

		// keep our custom view square, i.e. keep the width equal to the height 
		// regardless of what the MeasureSpec restrictions are
		int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		int widthSize = MeasureSpec.getSize(widthMeasureSpec);
		
		int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		int heightSize = MeasureSpec.getSize(heightMeasureSpec);
		
		int chosenWidth = chooseDimension(widthMode, widthSize);
		int chosenHeight = chooseDimension(heightMode, heightSize);
		
		int chosenDimension = Math.min(chosenWidth, chosenHeight);
		
		setMeasuredDimension(chosenDimension, (int) (chosenDimension*1.1));
	}
	
	/**
	 * Choose the size depending on the MeasureSpec restrictions
	 */
	private int chooseDimension(int mode, int size) {
		if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
			return size;
		} else { // (mode == MeasureSpec.UNSPECIFIED)
			return getPreferredSize();
		} 
	}
	
	/**
	 * in case there is no size specified
	 */
	private int getPreferredSize() {
		return 300;
	}

	/**
	 * @see android.view.View#onSizeChanged(int, int, int, int)
	 */
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		Log.d(TAG, "Size changed to " + w + "x" + h);
		regenerateBackground();
	}
	
	/**
	 * Regenerate the background
	 */
	private void regenerateBackground() {
		// free the old bitmap
		if (background != null) {
			background.recycle();
		}
		
		background = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
		Canvas backgroundCanvas = new Canvas(background);
		float scale = (float) getWidth();		
		backgroundCanvas.scale(scale, scale);
		
		drawRim(backgroundCanvas);
		drawFace(backgroundCanvas);
		drawSpeedScale(backgroundCanvas);
	}
	
	
	/**
	 * @see android.view.View#onDraw(android.graphics.Canvas)
	 */
	@Override
	protected void onDraw(Canvas canvas) {
		if (checkSpeedScale()) {
			regenerateBackground();
		}
		
		drawBackground(canvas);

		// Draw internal stuff only if there is something to draw
		if (data != null) {
			// Change the 2D coordinate space from width x height to 1.0 x 1.0
			// regardless of the current custom view size
			float scale = (float) getWidth();
			canvas.save(Canvas.MATRIX_SAVE_FLAG);
			canvas.scale(scale, scale);
			
			drawMaxSpeedHand(canvas);
			drawSpeedHand(canvas);
			drawAverageSpeedHand(canvas);
			drawSpeedText(canvas);
			drawIncline(canvas);
			drawdistance(canvas);
			
			canvas.restore();
			
			// Make speed hand move
			// (will auto generate a re-draw as needed, so must be called at the end)
			if (speedHandNeedsToMove()) {
				moveSpeedHand();
			}
		}
	}

	/**
	 * Draw the background, by reusing pre-drawn bitmap
	 */
	private void drawBackground(Canvas canvas) {
		if (background == null) {
			Log.w(TAG, "Background not created");
		} else {
			canvas.drawBitmap(background, 0, 0, backgroundPaint);
		}
	}

	/**
	 * Draw the white rim
	 */
	private void drawRim(Canvas canvas) {
		// Draw the white rim, with internal shadow
		canvas.drawOval(rimRect, rimPaint);
		canvas.drawOval(rimRect, rimShadowPaint);
		
		// Draw a black arc at the bottom
		canvas.drawArc(allRect, 45.0f, 90.0f, true, facePaint);
	}
	
	/**
	 * Draw the black face
	 */
	private void drawFace(Canvas canvas) {
		// Draw the black circle at the center
		canvas.drawOval(faceRect, facePaint);
		
		// Draw the speed unit
	    float x = 0.5f;
	    float y = 0.28f;
		canvas.drawText("km/h", x, y, speedUnitTextPaint);
	}

	/**
	 * Check whether the speed scale is OK for current speed
	 * and update the max speed scale if needed.
	 * Return true if the max speed scale has been modified.
	 */
	private boolean checkSpeedScale() {
		// Increase max scale speed if needed
		boolean needUpdate = false;
		if (data != null) {
			double maxSpeed = SpeedUnit.toKph(data.getSpeedMax());
			while (maxSpeed > maxScaleSpeed) {
				int step = (maxScaleSpeed < 60 ? 10 : 20);
				maxScaleSpeed = (int) Math.floor((maxScaleSpeed + step) / step) * step;
				needUpdate = true;
			}
		}
		return needUpdate;
	}
	
	/**
	 * Draw the scale for speed
	 */
	private void drawSpeedScale(Canvas canvas) {
		// Use rotation to draw all scales
		canvas.save(Canvas.MATRIX_SAVE_FLAG);
		canvas.rotate(235.0f, 0.5f, 0.5f);
		float tickAngle = 250.0f / maxScaleSpeed;
		for (int i = 0; i <= maxScaleSpeed; i++) {
			float y1 = scaleRect.top;
			float y_tiny = y1 + RIM_TICK_TINY; // bottom for tiny ticks
			float y_short = y1 + RIM_TICK_SHORT; // bottom for short ticks
			float y_long = y1 + RIM_TICK_LONG; // bottom for long ticks
			float y4 = y1 + RIM_TICK_LONG + 0.06f; // position for text
			
			if (i % 10 == 0) { // long ticks
				canvas.drawLine(0.5f, y1, 0.5f, y_long, scaleLongTickPaint);
			} else if (i % 5 == 0) { // short ticks
				canvas.drawLine(0.5f, y1, 0.5f, y_short, scaleShortTickPaint);
			} else { // tiny ticks
				canvas.drawLine(0.5f, y1, 0.5f, y_tiny, scaleTinyTickPaint);
			}
			
			// Draw text every X units, depending on max scale speed
			int textPeriod = (maxScaleSpeed < 80 ? 10 : 20);
			if (i % textPeriod == 0) {
				String valueString = Integer.toString(i);
				canvas.drawText(valueString, 0.5f, y4, scaleTextPaint);
			}

			canvas.rotate(tickAngle, 0.5f, 0.5f);
		}
		canvas.restore();
	}
	
	/**
	 * Set the speed hand target
	 */
	private void setSpeedHandTarget() {
		speedHandTarget = SpeedUnit.toKph(data.getSpeed());
	}

	/**
	 * Indicate whether we need to move speed hand from its current position
	 */
	private boolean speedHandNeedsToMove() {
		if (data == null) {
			return false;
		}
		
		double currentSpeed = SpeedUnit.toKph(data.getSpeed());
		return Math.abs(speedHandPosition - currentSpeed) > 0.01f;
	}
	
	/**
	 * Move hand
	 */
	private void moveSpeedHand() {
		if (! speedHandNeedsToMove()) {
			return;
		}
		if (data == null) {
			return;
		}
		
		if (lastSpeedHandMoveTime != -1L) {
			long currentTime = System.currentTimeMillis();
			// Delta in seconds since last move
			double delta = (currentTime - lastSpeedHandMoveTime) / 1000.0f;

			// Direction: 0 for not moving, -1 for decreasing, +1 for increasing
			int currentDirection = (speedHandVelocity == 0.0f ? 0 : (speedHandVelocity > 0 ? 1 : -1));
			int targetDirection = (speedHandTarget - speedHandPosition > 0 ? 1 : -1);

			// Calculate required acceleration
			double acceleration = 20.0f * (speedHandTarget - speedHandPosition) / maxScaleSpeed;
			
			// Calculate new velocity
			speedHandVelocity += acceleration * delta;
			double maxVelocity = maxScaleSpeed/2;
			if (speedHandVelocity < -maxVelocity) {
				speedHandVelocity = -maxVelocity;
			} else if (speedHandVelocity > maxVelocity) {
				speedHandVelocity = maxVelocity;
			}
			
			// Calculate new position
			speedHandPosition += speedHandVelocity * delta;
			if (speedHandPosition < 0) {
				speedHandPosition = 0.0f;
				speedHandVelocity = 0.0f;
			} else if (speedHandPosition > maxScaleSpeed) {
				speedHandPosition = maxScaleSpeed;
				speedHandVelocity = 0.0f;
			}
			
			if (Math.abs(speedHandTarget - speedHandPosition) < 0.05f) {
				// Hand closed enough to the target, stick to target
				speedHandPosition = speedHandTarget;
				speedHandVelocity = 0.0f;
				lastSpeedHandMoveTime = -1L;
			} else if (currentDirection != 0 && currentDirection != targetDirection) {
				// Direction changed, reset velocity
				speedHandVelocity = 0.0f;
				lastSpeedHandMoveTime = System.currentTimeMillis();
			} else {
				// Continue
				lastSpeedHandMoveTime = System.currentTimeMillis();
			}
			
			// Calculate new max position
			if (speedHandPosition > speedHandMaxPosition) {
				speedHandMaxPosition = speedHandPosition;
			}
			
			// Redraw
			invalidate();
		} else {
			lastSpeedHandMoveTime = System.currentTimeMillis();
			moveSpeedHand();
		}
	}

	/**
	 * Draw the speed hand in the scale
	 */
	private void drawSpeedHand(Canvas canvas) {
		float angle = (float) (235.0f + (250.0f * speedHandPosition / maxScaleSpeed));

		canvas.save(Canvas.MATRIX_SAVE_FLAG);
		canvas.rotate(angle, 0.5f, 0.5f);
		canvas.drawPath(speedHandPath, speedHandPaint);
		canvas.restore();
	}

	/**
	 * Draw the average speed hand in the scale
	 */
	private void drawAverageSpeedHand(Canvas canvas) {
		double avgSpeed = SpeedUnit.toKph(data.getSpeedAverage());
		float angle = (float) (235.0f + (250.0f * avgSpeed / maxScaleSpeed));

		canvas.save(Canvas.MATRIX_SAVE_FLAG);
		canvas.rotate(angle, 0.5f, 0.5f);
		canvas.drawPath(averageSpeedHandPath, averageSpeedHandPaint);
		canvas.restore();
	}

	/**
	 * Draw the max speed hand in the scale
	 */
	private void drawMaxSpeedHand(Canvas canvas) {
		// Do not use max speed provided by data object (data.getMaxSpeed())
		// Instead calculate max speed reached by speed hand
		// This avoids the max speed to do sudden moves to higher value
		double maxSpeed = speedHandMaxPosition;
		// Draw only if max pos is different than current pos
		if (maxSpeed > speedHandPosition) {
			float angle = (float) (235.0f + (250.0f * maxSpeed / maxScaleSpeed));

			canvas.save(Canvas.MATRIX_SAVE_FLAG);
			canvas.rotate(angle, 0.5f, 0.5f);
			canvas.drawPath(speedHandPath, maxSpeedHandPaint);
			canvas.restore();
		}
	}
	
	/**
	 * Draw the speed text
	 */
	private void drawSpeedText(Canvas canvas) {
		double speed = SpeedUnit.toKph(data.getSpeed());
		String speedStr = rounder0.format(speed);

	    float x = 0.52f;
	    float y = 0.61f;
		canvas.drawText(speedStr, x, y, speedTextPaint);
	}

	/**
	 * Draw the incline arc
	 */
	private void drawIncline(Canvas canvas) {
		float angle = (float) - (2d * data.getInclineDeg());

		// Rotate canvas
		canvas.save(Canvas.MATRIX_SAVE_FLAG);
		canvas.rotate(angle, 0.5f, 0.5f);
		
		// Draw inclined ground
		canvas.drawPath(inclinePath, inclinePaint);
		
		// Restore canvas rotation
		canvas.restore();

		// Draw incline value
		String inclineStr = rounder0.format(data.getInclinePer()*100) + "%";
		if ("-0%".equals(inclineStr)) {
			inclineStr = "0%";
		}
		float x = 0.5f;
		float y = 0.76f;
		canvas.drawText(inclineStr, x, y, inclineTextPaint);
	}
	
	/**
	 * Draw the distance
	 */
	private void drawdistance(Canvas canvas) {
		double distance = data.getTotalDistance() / 1000; // convert to km
		String distanceStr = rounderOdo.format(distance);

		canvas.drawText(distanceStr, 0.5f, 0.95f, distancePaint);
	}
	
	
	/**
	 * Update data
	 */
	public void setPositionData(SpeedoGradientData data) {
		this.data = data;
		setSpeedHandTarget();
		invalidate();
	}
	
	/**
	 * Action to be performed when the preferences have been changed.
	 * Reset and redraw the whole display
	 */
	public void onPreferenceChanged(SharedPreferences sharedPreferences, String key) {
		if ("pref_maxSpeed".equals(key)) {
			configure(sharedPreferences);
			regenerateBackground();
		}
	}
}
